package com.nativelibs4java.buswing;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.JTableHeader;
import javax.swing.tree.TreePath;

import org.jdesktop.swingx.JXCollapsiblePane;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTreeTable;

public class JMiningTable extends JPanel {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;


	MiningTreeTableModel model;
	

	final JXCollapsiblePane groupPanel;
	final Box groupBox;
	final JXTreeTable treeTable;
    final JXTable table;
	
	MiningColumn draggedColumn;

    List<JXCollapsiblePane> collapsibleInserts = new ArrayList<JXCollapsiblePane>();
    List<JButton> groupButtons = new ArrayList<JButton>();

    Dimension noSpace = new Dimension(0, 0);
    JXCollapsiblePane getInsert(int i) {
        JXCollapsiblePane p = i < collapsibleInserts.size() ? collapsibleInserts.get(i) : null;
        if (p == null) {
            //http://fr.wikipedia.org/wiki/Table_des_caract%C3%A8res_Unicode/U2900
            JLabel l = new JLabel(System.getProperty("os.name").contains("Mac") ? "\u2935" : "x");
            l.setMaximumSize(noSpace);
            p = new JXCollapsiblePane(JXCollapsiblePane.Direction.RIGHT, new BorderLayout());
            p.add(l, "Center");
            p.setCollapsed(true);
            listenDrag(p);
            p.setMaximumSize(new Dimension(5, 100));
            for (int x = 1 + i - collapsibleInserts.size(); x-- != 0;)
                collapsibleInserts.add(null);
            collapsibleInserts.set(i, p);
        }
        return p;
    }
    int getCurrentInsertIndex() {
        for (int i = 0, n = collapsibleInserts.size(); i < n; i++) {
            JXCollapsiblePane p = collapsibleInserts.get(i);
            if (!p.isCollapsed())
                return i;
        }
        return -1;
    }
    void expandInsert(int index) {
        for (int i = 0, n = collapsibleInserts.size(); i < n; i++) {
            JXCollapsiblePane p = collapsibleInserts.get(i);
            boolean collapsed = i != index;
            ((JComponent)p.getComponent(0)).setMaximumSize(collapsed ? noSpace : null);
            if (collapsed != p.isCollapsed()) {
                //System.out.println("p[" + i + "].setCollapsed(" + collapsed + ")");
            }
            p.setCollapsed(collapsed);
        }
    }

	private void updateGroupPanel() {
        groupButtons.clear();

		groupPanel.setCollapsed(true);
		groupBox.removeAll();
		if (model != null) {
            List<MiningColumn> groupColumns = model.getGroupColumns();
            JXCollapsiblePane insert = getInsert(0);
            groupBox.add(insert);
            if (groupColumns.isEmpty()) {
                showTable();
                groupBox.add(Box.createHorizontalStrut(10));
                listenDrag(groupBox.add(new JLabel("<html><body><i>Drop columns here for breakdown analysis</i>")));
            } else {
                showTreeTable();
                

                int iGroup = 0;
                for (MiningColumn column : groupColumns) {
                    JButton b = newColumnGroupButton(column);
                    groupBox.add(b);
                    groupButtons.add(b);

                    insert = getInsert(iGroup + 1);
                    groupBox.add(insert);
                    iGroup++;
                }
                if (getModel().getVisibleColumns().size() > 1) {
                    groupBox.add(Box.createHorizontalStrut(10));
                    listenDrag(groupBox.add(new JLabel("<html><body><i>drop other columns here</i>")));
                }
                
            }
            groupBox.add(Box.createHorizontalGlue());
            JButton closeButton = newGroupStyledButton();
            closeButton.setText("\u2573");
            closeButton.setToolTipText("Clear grouping");
            closeButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    setGroupColumns(Collections.EMPTY_LIST);
                }
            });
            listenDrag(closeButton);

            groupBox.add(closeButton);
            groupBox.invalidate();
			groupPanel.setCollapsed(groupColumns.isEmpty() && draggedColumn == null);
		}
//		else
//			groupPanel.setCollapsed(true);
		
	}

    private JButton newColumnGroupButton(MiningColumn column) {
        JButton b = newGroupStyledButton();
        if (column != null) {
            listenDrag(b);
            b.setToolTipText("Expand / Collapse all '" + column.getName() + "' nodes");
            b.setText(column.getName());
            b.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    MiningColumn column = getColumn((JButton)e.getSource());
                    List<MiningNode> groupNodes = getModel().getGroupNodes(column);
                    if (makeVisible(groupNodes))
                        return;

                    List<TreePath>
                        expanded = new ArrayList<TreePath>(),
                        collapsed = new ArrayList<TreePath>();

                    for (MiningNode node : groupNodes) {
                        TreePath path = node.getPath();
                        if (treeTable.isExpanded(path))
                            expanded.add(path);
                        else
                            collapsed.add(path);
                    }
                    if (expanded.size() < groupNodes.size())
                        for (TreePath path : collapsed)
                            treeTable.expandPath(path);
                    else
                        for (TreePath path : expanded)
                            treeTable.collapsePath(path);

                    //treeTable.setExpandsSelectedPaths(true);
                }
            });
            b.putClientProperty(MiningColumn.class, column);
        }
        return b;
    }

    private void listenDrag(Component add) {
        add.addMouseMotionListener(mouseDragListener);
        add.addMouseListener(mouseDragListener);
    }

    class ContainerPane extends JPanel implements JXCollapsiblePane.CollapsiblePaneContainer {

        public ContainerPane(LayoutManager m) {
            super(m);
        }
        @Override
        public Container getValidatingContainer() {
            return this;
        }

    }
    JPanel tablesPanel;
    CardLayout tablesCardLayout = new CardLayout();
	public JMiningTable() {
		super(new BorderLayout());
        groupPanel = new JXCollapsiblePane(new BorderLayout());
        JPanel intermediate = new ContainerPane(new BorderLayout());
		groupBox = Box.createHorizontalBox();
		//groupPanel.add(new JLabel("Grouping :"), "West");
		intermediate.add(groupBox, "Center");
        groupPanel.add(intermediate, "Center");
        Insets insets = getInsets();
        groupBox.setMinimumSize(new Dimension(0, newGroupStyledButton().getPreferredSize().height + insets.top + insets.bottom));

		treeTable = new JXTreeTable();
        table = new JXTable();

        treeTable.setAutoResizeMode(JXTreeTable.AUTO_RESIZE_ALL_COLUMNS);
        table.setAutoResizeMode(JXTreeTable.AUTO_RESIZE_ALL_COLUMNS);

//        for (int i = 0; i < 10; i++)
//            getInsert(i);
		
        listenDrag(groupBox);

        for (JTableHeader header : new JTableHeader[] { treeTable.getTableHeader(), table.getTableHeader() }) {
            listenDrag(header);
        }

        tablesPanel = new JPanel(tablesCardLayout);
        tablesPanel.add(new JScrollPane(treeTable), "treeTable");
		tablesPanel.add(new JScrollPane(table), "table");
        showTable();
		add("Center", tablesPanel);
		add("North", groupPanel);
		updateGroupPanel();
	}

	public JMiningTable(MiningTreeTableModel model) {
		this();
		setModel(model);
	}
    public void packAll() {
        table.packAll();
        treeTable.packAll();
    }
    public void setGroupColumns(List<MiningColumn> columns) {
        Enumeration exp = treeTable.getExpandedDescendants(getModel().getRoot().getPath());
        getModel().setGroupColumns(columns);
        while (exp.hasMoreElements()) {
            TreePath path = (TreePath)exp.nextElement();
            expandNode(path, true);
        }
    }
    public void expandNode(TreePath path, boolean makeVisible) {
        if (makeVisible)
            makeVisible(path);
        treeTable.expandPath(path);
    }
	MouseAdapter mouseDragListener = new MouseAdapter() {
		boolean dragged, exited;

		public void mouseReleased(MouseEvent e) {
            if (draggedColumn != null && dragged && exitedOrNotHeader(e)) {
                List<MiningColumn> columns = new ArrayList<MiningColumn>(getModel().getGroupColumns());
                columns.remove(draggedColumn);
                if (groupPanel.getMousePosition() != null) {
                    int i = getCurrentInsertIndex();
                    if (i < 0)
                        return;
                    if (draggedColumn.getGroupIndex() >= 0 && draggedColumn.getGroupIndex() < i)
                        i--;
                    if (i >= 0 && i < columns.size())
                        columns.add(i, draggedColumn);
                    else
                        columns.add(draggedColumn);
					final MiningColumn dragged = draggedColumn;
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            for (JButton b : groupButtons) {
                                if (getColumn(b) == dragged) {
                                    b.requestFocus();
                                    break;
                                }
                            }
                        }

                    });
				}
				setGroupColumns(columns);
                updateGroupPanel();
			}
            draggedColumn = null;
            expandInsert(-1);
            dragged = false;
		}

		public void mouseDragged(MouseEvent e) {
            if (draggedColumn != null) {
                if (!dragged) {
                    dragged = true;
                    if (groupPanel.isCollapsed())
                        updateGroupPanel();
                }
                updateInserts();
            }
        }

        public void mousePressed(MouseEvent e) {
            dragged = false;
            exited = false;
			if (e.getComponent() instanceof JTableHeader) {
                JTableHeader header = (JTableHeader)e.getComponent();
                List<MiningColumn> visibleColumns = getModel().getVisibleColumns();
				if (draggedColumn == null && visibleColumns.size() > 1) {
                    int i = header.getColumnModel().getColumnIndexAtX(e.getX());
                    if (i >= 0) {
                        draggedColumn = visibleColumns.get(i);
                    }
				}

			}
            if (e.getComponent() instanceof JButton) {
                if (draggedColumn == null) {
                    draggedColumn = getColumn((JButton)e.getComponent());
                }
            }
		}

        @Override
        public void mouseExited(MouseEvent e) {
            exited = true;
        }


        private boolean exitedOrNotHeader(AWTEvent e) {
            return (exited || !(e.getSource() instanceof JTableHeader));
        }
	};

    MiningColumn getColumn(JButton b) {
        return (MiningColumn)b.getClientProperty(MiningColumn.class);
    }
    void updateInserts() {

        int insertIndex = -1;
        if (getModel().getGroupColumns().isEmpty())
            insertIndex = 0;
        else {
            Point p = groupBox.getMousePosition();
            if (p != null && draggedColumn != null) {
                int x = p.x;
                int buttonCount = groupButtons.size();
                int draggedIndex = draggedColumn.getGroupIndex();
                if (draggedIndex < 0 || draggedIndex != buttonCount - 1)
                    insertIndex = buttonCount;
                for (int buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) {
                    JButton b = groupButtons.get(buttonIndex);
                    int bX = b.getLocation().x, bW = b.getWidth();
                    int centerX = bX + bW / 2;
                    boolean inside = x >= bX && x < bX + bW;
                    if (inside && draggedIndex == buttonIndex) {
                        insertIndex = -1;//buttonIndex == 0 ? -1 : buttonIndex - 1;
                        break;
                    } else if (x < centerX) {
                        if (draggedIndex >= 0 && draggedIndex == buttonIndex - 1 && inside) {
                            insertIndex = buttonIndex + 1;
                        } else {
                            insertIndex = buttonIndex;
                        }
                        break;
                    } else if (inside && draggedIndex == buttonIndex + 1) {
                        insertIndex = buttonIndex;
                        break;
                    }
                }
            }
        }
        expandInsert(insertIndex);
    }
    public MiningTreeTableModel getModel() {
        //return (MiningTreeTableModel) treeTable.getModel();
        return model;
    }
	public void setModel(MiningTreeTableModel model) {
        List<String> groupNames = new ArrayList<String>();
        if (this.model != null) {
            for (MiningColumn col : this.model.getGroupColumns())
                groupNames.add(col.getName());
        }
		this.model = model;
		if (model != null) {
			model.addMiningModelListener(new MiningModelListener() {
				@Override
				public void groupColumnsChanged(MiningModelEvent evt) {
					updateGroupPanel();
                    packAll();
				}
			});
		}
		treeTable.setTreeTableModel(model);
        table.setModel(model);

        if (!groupNames.isEmpty() && model.getGroupColumns().isEmpty()) {
            List<MiningColumn> cols = new ArrayList<MiningColumn>();
            for (String groupName : groupNames) {
                MiningColumn col = model.getColumn(groupName);
                if (col != null)
                    cols.add(col);
            }
            model.setGroupColumns(cols);
        } else
            updateGroupPanel();
	}

    public void expandAll() {
        List<MiningColumn> groupColumns = getModel().getGroupColumns();
        if (groupColumns.isEmpty())
            return;

        for (MiningNode node : getModel().getGroupNodes(groupColumns.get(groupColumns.size() - 1)))
            treeTable.expandPath(node.getPath());
    }
    public boolean makeVisible(Iterable<MiningNode> nodes) {
        boolean didSomething = false;
        for (MiningNode node : nodes)
            didSomething = makeVisible(node.getPath()) || didSomething;
        return didSomething;
    }
    public boolean makeVisible(TreePath path) {
        boolean didSomething = false;
        TreePath parent = path.getParentPath();
        if (parent != null)
            didSomething = makeVisible(parent);

        if (!treeTable.isExpanded(path)) {
            treeTable.expandPath(path);
            didSomething = true;
        }
        return didSomething;
    }
    private JButton newGroupStyledButton() {
        JButton b = new JButton("|");
        b.putClientProperty("JButton.buttonType", "gradient");
        return b;
    }

    private void showTable() {
        tablesCardLayout.show(tablesPanel, "table");
    }
    private void showTreeTable() {
        tablesCardLayout.show(tablesPanel, "treeTable");
    }

}